package com.googlecode.dgwt.client.dijit.util;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Node;
import com.google.gwt.http.client.Request;
import com.google.gwt.http.client.RequestCallback;
import com.google.gwt.http.client.RequestException;
import com.google.gwt.http.client.Response;
import com.googlecode.dgwt.client.dijit.Dijit;
import com.googlecode.dgwt.client.dijit.util.impl.TemplateImpl;
import com.googlecode.dgwt.client.dojo.Dojo;
import com.googlecode.dgwt.client.dojo.URL;
import com.googlecode.dgwt.client.dojo.callback.SimpleCallback;
import com.googlecode.dgwt.client.dojo.callback.TransformCallback;
import com.googlecode.dgwt.client.dojo.util.StringMap;


/**
 * @author	Wellington Tadeu dos Santos
 * @since	DGWT v0.01
 */
public class Template {

    
    @SuppressWarnings("unused")
    private static final TemplateImpl impl = GWT.create(TemplateImpl.class);
    
    private static final Map<String, Element> CACHED_TEMPLATES = new StringMap<Element>(); 
    private static final Map<String, Element> REMOTE_TEMPLATES = new StringMap<Element>(); 

    
    private final int BLANK = 0;
    private final int LOADING = 1;
    private final int LOADED = 2;
    private final int ERROR = 3;
    
    private URL templatePath = null;
    private Element templateNode = null;
    private String templateString = null;
    private boolean spanForCollection = false;
    private boolean skipNodeCache = false;
    private int loadedState = BLANK;
    
    private Map<String, Element> attachments = new StringMap<Element>();
    private List<String> styleSheets = new ArrayList<String>();
    private List<String> scripts = new ArrayList<String>();
    

    public Template(Element templateNode){
        this.templateNode = templateNode;
    }
    
    
    public Template(String templateString){
        this.templateString = templateString;
    }
    
    
    public Template(URL templatePath){
        this.templatePath = templatePath;
    }
    
    
    public Template(String templateString, boolean skipNodeCache){
        this.templateString = templateString;
        this.skipNodeCache = skipNodeCache;
    }

    
    public Template(URL templatePath, boolean skipNodeCache){
        this.templatePath = templatePath;
        this.skipNodeCache = skipNodeCache;
    }
    

    public void load(){
        load(null, null);
    }

    
    public void load(TemplateHandler handler){
        load(handler, null);
    }

    
    public void load(SimpleCallback<Template> callback){
        load(null, callback);
    }
    
    public void load(TemplateHandler handler, SimpleCallback<Template> callback){
        if (loadedState == BLANK){
            loadedState = LOADING;
            
            if (templateNode != null){
                loaded(handler, callback, setupElement(templateNode));    			
            } else {
                fetchContent(handler, callback);
            }
        }
    }
    
    
    protected void loaded(TemplateHandler handler, SimpleCallback<Template> callback, Element domElement){
        loadedState = LOADED;
        attachments.put("domElement", domElement);
        if (callback != null){
            callback.callback(this);
        }
    }
    

    protected void fetchContent(final TemplateHandler handler, final SimpleCallback<Template> callback){
        
        Element node;
        String key;
        
        if (templatePath != null){
            if (!skipNodeCache){
                key = templatePath.getUrl(); // TODO pegar url absoluta
                node = REMOTE_TEMPLATES.get(key);
                
                if (node != null) {
                    if (!node.getPropertyBoolean("ownerDocument") || node.getOwnerDocument() == Document.get()) {
                        
                        loaded(handler, callback, setupElement(node.cloneNode(true).<Element>cast()));
                        return;
                    }
                    Dojo.destroy(node);
                    REMOTE_TEMPLATES.remove(key);
                }
            }
            
            try {
                if (callback != null){					
                    
                    this.templatePath.sendRequest(null, new RequestCallback() {
                        
                        @Override
                        public void onResponseReceived(Request request, Response response) {
                            templateString = sanitizeTemplateString(response.getText());							
                            loaded(handler, callback, instantiate(handler));
                        }
                        
                        @Override
                        public void onError(Request request, Throwable exception) {
                            loadedState = ERROR;
                            // TODO(wellsantos) o que fazer com este erro ??
                            throw new RuntimeException(exception);
                        }
                        
                    });
                    
                    return;
                } else {
                    templateString = sanitizeTemplateString(templatePath.fetch().getText());
                }
                
            } catch (RequestException e) {
                loadedState = ERROR;
                throw new RuntimeException(e); // FIXME RuntimeException?
            }
            
        } else {
            if (templateString != null){
                templateString = templateString.trim();
            } else {
                templateString = "";
            }
        }
        
        loaded(handler, callback, instantiate(handler));
    }
    
    
    protected Element instantiate(final TemplateHandler handler){
        
        Element node;
        String key;

        // verifica se um template pode ser obtido do cache
        
        boolean parametrizado = skipNodeCache;
        if (!parametrizado){
            parametrizado = Dojo.string.isParameterized(templateString);
        }
        
        if (parametrizado) {
            
            // no-cache: substituição de parâmetros ${x}
            
            node = toDOM(Dojo.string.substitute(templateString, new TransformCallback<String,String>() {
                public String transform(String key, String value) {
                                        
                    if ((value = handler.getTemplateParameter(key)) != null) {
                        return Dojo.string.escapeHTML(value);
                    }
                    
                    return "";
                }
            }));

        } else {

            if (templatePath == null){
                node = CACHED_TEMPLATES.get(key = templateString);
                
                if (node != null) {
                    if (node.getOwnerDocument() == null || node.getOwnerDocument() == Document.get()) {
                        node = node.cloneNode(true).cast();
                        return setupElement(node);
                    }
                    Dojo.destroy(node);
                }

                node = toDOM(templateString);
                CACHED_TEMPLATES.put(key, (Element)node.cloneNode(true).cast());
                
            } else {
                
                key = templatePath.getUrl(); // TODO pegar url absoluta
                node = toDOM(templateString);
                REMOTE_TEMPLATES.put(key, (Element)node.cloneNode(true).cast());
            }
        }

        return setupElement(node);
    }
    
    
    private Element setupElement(Element node){
        
        Element domElement = node;

        for (Element element : Dojo.query("[dojoAttachPoint]", node).<Element>cast()){
            String attachs = element.getAttribute("dojoAttachPoint");
            if (attachs != null){
                for (String attach : attachs.split("\\s*,\\s*")){
                    if (!"".equals(attach)){
                        if ("domElement".equals(attach)){
                            domElement = element;
                        } else {
                            attachments.put(attach, element);
                        }
                    }
                }
            }
        }
        
        for (Element element : Dojo.query("[waiRole]", node).<Element>cast()){
            String waiRole = element.getAttribute("waiRole");
            if (waiRole != null) {
                Dijit.setWaiRole(element, waiRole);
            }
        }
        
        for (Element element : Dojo.query("[waiState]", node).<Element>cast()){
            String waiState = element.getAttribute("waiState");
            if (waiState != null) {
                for (String item : waiState.split("\\s*,\\s*")){
                    if (item.indexOf('-') >= 0){
                        String[] items = item.split("-");
                        Dijit.setWaiState(element, items[0], items[1]);
                    }
                }
            }
        }
        
        return domElement;
    }


    protected Element toDOM(String html) { 
        
        Node node = Dojo.toDom(html, null, spanForCollection ? "span" : null);
    
        // ensure to return an ELEMENT_NODE
        if (!Element.is(node)){
            Element newElement = Document.get().createSpanElement();
            newElement.appendChild(node);
            return newElement;
        }
        
        return node.cast();
    }
    
    
    protected String sanitizeTemplateString(String html){
        return Dojo.html.snarf(html, styleSheets, scripts);
    }
    
    
    public void setTemplatePath(URL templatePath) {
        this.templatePath = templatePath;
    }

    public URL getTemplatePath() {
        return templatePath;
    }

    public void setTemplateString(String templateString) {
        this.templateString = templateString;
    }

    public String getTemplateString() {
        return templateString;
    }
    
    public void setSkipNodeCache(boolean skipNodeCache) {
        this.skipNodeCache = skipNodeCache;
    }

    public boolean isSkipNodeCache() {
        return skipNodeCache;
    }

    public void setSpanForCollection(boolean useSpanForCollection) {
        this.spanForCollection = useSpanForCollection;
    }

    public boolean isSpanForCollection() {
        return spanForCollection;
    }

    public boolean isLoading() {
        return loadedState == LOADING;
    }

    public boolean isLoaded() {
        return loadedState == LOADED;
    }

    public Map<String, Element> getAttachments() {
        return attachments;
    }

    public List<String> getStyleSheets() {
        return styleSheets;
    }

    public List<String> getScripts() {
        return scripts;
    }
    

    static {
        /* TODO
         
            if (dojo.isIE) {
                dojo.addOnWindowUnload(function(){
                    var _29 = dijit._Templated._templateCache;
                    for (var key in _29) {
                        var _2b = _29[key];
                        if (!isNaN(_2b.nodeType)) {
                            dojo.destroy(_2b);
                        }
                        delete _29[key];
                    }
                });
            }
 
     */
    }
}
